package com.jetpackframework.virtual;

import android.os.Build;
import android.util.Log;

import com.jetpackframework.Reflector;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class ClassLoaderUtil {
    /**
     * 将dexPath中的文件放到classLoader的dexPathList变量的最前面
     * @param dexFile
     * @param optimizedDir
     * @param classLoader
     */
    public static void injectDexesInternal(List<File> dexFile, File optimizedDir, ClassLoader classLoader) {
        if (Build.VERSION.SDK_INT >= 23){
            V23.install(dexFile,optimizedDir,classLoader);
        }else if (Build.VERSION.SDK_INT >= 19){
            V19.install(dexFile,optimizedDir,classLoader);
        }else if (Build.VERSION.SDK_INT >= 14){
            V14.install(dexFile,optimizedDir,classLoader);
        }else if (Build.VERSION.SDK_INT >= 4){
            V4.install(dexFile,optimizedDir,classLoader);
        }
    }

    private static void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws Reflector.ReflectedException {
        Object[] fieldArray = Reflector.with(instance).field(fieldName).get();
        Object[] newArray = (Object[]) Array.newInstance(fieldArray.getClass().getComponentType(),fieldArray.length+extraElements.length);
        System.arraycopy(extraElements,0,newArray,0,extraElements.length);
        System.arraycopy(fieldArray,0,newArray,extraElements.length,fieldArray.length);
        Reflector.with(instance).field(fieldName).set(newArray);
    }
    private static final class V23{
        public static void install(List<File> dexFile, File optimizedDir, ClassLoader classLoader){
            try {
                Object pathList = Reflector.with(classLoader).field("pathList").get();
                ArrayList<IOException> exceptions = new ArrayList<>();
                Object[] dexElements = makeElements(pathList,new ArrayList<>(dexFile),optimizedDir,exceptions);
                expandFieldArray(pathList,"dexElements",dexElements);
            } catch (Reflector.ReflectedException e) {
                e.printStackTrace();
            }
        }

        private static Object[] makeElements(Object pathList, ArrayList<File> files, File optimizedDir, ArrayList<IOException> exceptions) throws Reflector.ReflectedException {
            try {
                return Reflector.with(pathList).method("makePathElements",List.class,File.class,List.class).call(files,optimizedDir,exceptions);
            } catch (Reflector.ReflectedException e) {
                try {
                    return Reflector.with(pathList).method("makePathElements",ArrayList.class,File.class,ArrayList.class).call(files,optimizedDir,exceptions);
                } catch (Reflector.ReflectedException reflectedException) {
                    try {
                        return V19.makeElements(pathList,files,optimizedDir,exceptions);
                    } catch (Reflector.ReflectedException exception) {
                        throw exception;
                    }
                }
            }
        }
    }
    private static final class V19{
        public static void install(List<File> dexFile, File optimizedDir, ClassLoader classLoader){
            try {
                Object pathList = Reflector.with(classLoader).field("pathList").get();
                ArrayList<IOException> exceptions = new ArrayList<>();
                Object[] dexElements = makeElements(pathList,new ArrayList<>(dexFile),optimizedDir,exceptions);
                expandFieldArray(pathList,"dexElements",dexElements);
            } catch (Reflector.ReflectedException e) {
                e.printStackTrace();
            }
        }

        private static Object[] makeElements(Object pathList, ArrayList<File> files, File optimizedDir, ArrayList<IOException> exceptions) throws Reflector.ReflectedException {
            try {
                return Reflector.with(pathList).method("makeDexElements",ArrayList.class,File.class,ArrayList.class).call(files,optimizedDir,exceptions);
            } catch (Reflector.ReflectedException e) {
                return Reflector.with(pathList).method("makeDexElements",List.class,File.class,List.class).call(files,optimizedDir,exceptions);
            }
        }
    }
    private static final class V14{
        public static void install(List<File> dexFile, File optimizedDir, ClassLoader classLoader){

        }
    }
    private static final class V4{
        public static void install(List<File> dexFile, File optimizedDir, ClassLoader classLoader){

        }
    }
}
